package org.greatlogic.gwtfilereaderwidget.client.itunes;

import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Node;
import com.google.web.bindery.event.shared.EventBus;
import java.util.TreeSet;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EITunesEventType;
import org.greatlogic.gwtfilereaderwidget.client.xml.XMLEnums.EXMLEscapeSequence;

final class ITunesUtil {
//--------------------------------------------------------------------------------------------------
private static EventBus _eventBus;
//--------------------------------------------------------------------------------------------------
public static String escapeValue(final String source, final boolean attributeValue) {
  String result = source;
  if (result == null) {
    result = "";
  }
  else {
    EXMLEscapeSequence escapeSequence = null;
    StringBuilder sb = null;
    for (int i = 0; i < result.length(); ++i) {
      if (attributeValue) {
        if (result.charAt(i) == '\'') {
          escapeSequence = EXMLEscapeSequence.Apostrophe;
        }
      }
      else {
        switch (result.charAt(i)) {
          case '<':
            escapeSequence = EXMLEscapeSequence.LessThan;
            break;
          case '>':
            escapeSequence = EXMLEscapeSequence.GreaterThan;
            break;
          case '&':
            escapeSequence = EXMLEscapeSequence.Ampersand;
            break;
          case '"':
            escapeSequence = EXMLEscapeSequence.Quote;
            break;
          case '\'':
            escapeSequence = EXMLEscapeSequence.Apostrophe;
            break;
        }
      }
      if (escapeSequence != null) {
        if (sb == null) {
          sb = new StringBuilder(source);
        }
        sb.replace(i, i + 1, "&" + escapeSequence.getEscapeString() + ";");
        i += escapeSequence.getEscapeString().length() + 1;
        escapeSequence = null;
      }
    }
    if (sb != null) {
      result = sb.toString();
    }
  }
  return result;
} // escapeValue()
//--------------------------------------------------------------------------------------------------
static void fireEvent(final EITunesEventType eventType) {
  fireEvent(eventType, null, 0, 0);
} // fireEvent()
//--------------------------------------------------------------------------------------------------
static void fireEvent(final EITunesEventType eventType, final String message) {
  fireEvent(eventType, message, 0, 0);
} // fireEvent()
//--------------------------------------------------------------------------------------------------
static void fireEvent(final EITunesEventType eventType, final String message,
                      final int targetCount, final int completedCount) {
  fireEvent(eventType, message, targetCount, completedCount, null, null);
} // fireEvent()
//--------------------------------------------------------------------------------------------------
public static void fireEvent(final EITunesEventType eventType, final String message,
                             final TreeSet<String> unknownKeyTypeSet,
                             final TreeSet<String> unknownKeyDataTypeSet) {
  fireEvent(eventType, message, 0, 0, unknownKeyTypeSet, unknownKeyDataTypeSet);
} // fireEvent()
//--------------------------------------------------------------------------------------------------
public static void fireEvent(final EITunesEventType eventType, final String message,
                             final int targetCount, final int completedCount,
                             final TreeSet<String> unknownKeyTypeSet,
                             final TreeSet<String> unknownKeyDataTypeSet) {
  if (_eventBus != null) {
    _eventBus.fireEvent(new ITunesEvent(eventType, message, targetCount, completedCount,
                                        unknownKeyTypeSet, unknownKeyDataTypeSet));
  }
} // fireEvent()
//--------------------------------------------------------------------------------------------------
static String getElementValue(final Element element) {
  String result;
  Node childNode = element.getFirstChild();
  while (childNode != null && childNode.getNodeType() != Node.TEXT_NODE) {
    childNode = childNode.getNextSibling();
  }
  result = childNode == null ? "" : childNode.getNodeValue();
  return result == null ? "" : result;
} // getElementValue()
//--------------------------------------------------------------------------------------------------
static Element getFirstChildElement(final Element parentElement) throws ITunesException {
  return getFirstChildElement(parentElement, null, null, null);
} // getFirstChildElement()
//--------------------------------------------------------------------------------------------------
static Element getFirstChildElement(final Element parentElement, final String elementTag)
  throws ITunesException {
  return getFirstChildElement(parentElement, elementTag, null, null);
} // getFirstChildElement()
//--------------------------------------------------------------------------------------------------
static Element getFirstChildElement(final Element parentElement, final String elementTag,
                                    final String elementValue, final String exceptionMessage)
  throws ITunesException {
  Element result;
  Node childNode = parentElement.getFirstChild();
  while (childNode != null && childNode.getNodeType() != Node.ELEMENT_NODE) {
    childNode = childNode.getNextSibling();
  }
  result = (Element)childNode;
  while (result != null &&
         ((elementTag != null && !result.getNodeName().equals(elementTag)) || (elementValue != null && !ITunesUtil.getElementValue(result).equals(elementValue)))) {
    result = ITunesUtil.getNextSiblingElement(result);
  }
  if (result == null && exceptionMessage != null) {
    throw new ITunesException(exceptionMessage);
  }
  return result;
} // getFirstChildElement()
//--------------------------------------------------------------------------------------------------
static Element getNextSiblingElement(final Element previousSiblingElement) {
  return getNextSiblingElement(previousSiblingElement, null);
} // getNextSiblingElement()
//--------------------------------------------------------------------------------------------------
static Element getNextSiblingElement(final Element previousSiblingElement, final String elementTag) {
  Node result = previousSiblingElement == null ? null : previousSiblingElement.getNextSibling();
  while (result != null &&
         (result.getNodeType() != Node.ELEMENT_NODE || (elementTag != null && !result.getNodeName().equals(elementTag)))) {
    result = result.getNextSibling();
  }
  return (Element)result;
} // getNextSiblingElement()
//--------------------------------------------------------------------------------------------------
/**
* Converts a string that may contain hexadecimal digits to the long integer value. If the string
* contains any non-hexadecimal characters then the default value will be returned.
* @param hexString A string containing hexadecimal digits.
* @param defaultValue The value that will be returned if the string contains any non-hexadecimal
* characters.
* @return The decimal integer value of the hexadecimal string, or the default value if the string
* contains any non-hexadecimal characters.
*/
public static long hexToLong(final String hexString, final long defaultValue) {
  long result;
  try {
    result = Long.parseLong(hexString, 16);
  }
  catch (NumberFormatException nfe) {
    result = defaultValue;
  }
  return result;
} // hexToLong()
//--------------------------------------------------------------------------------------------------
public static boolean isBlank(final String s) {
  return s == null || s.isEmpty() || (s.charAt(0) == ' ' && s.trim().isEmpty());
} // isBlank()
//--------------------------------------------------------------------------------------------------
static void setEventBus(EventBus eventBus) {
  _eventBus = eventBus;
} // setEventBus()
//--------------------------------------------------------------------------------------------------
/**
* Converts a string value to its equivalent boolean value. The values "y" and "true", which are
* compared with case ignored, are interpreted as true. If the string is null or is zero length then
* the default value is returned.
* @param stringValue The string that will be converted.
* @param defaultValue The value that will be returned if the string is null or zero length.
* @return true if the string begins with "y" or equals "true" (ignoring case).
*/
public static boolean stringToBoolean(final String stringValue, final boolean defaultValue) {
  boolean result;
  if (isBlank(stringValue)) {
    result = defaultValue;
  }
  else {
    result = stringValue.charAt(0) == 'y' || stringValue.charAt(0) == 'Y' ||
             stringValue.toString().equalsIgnoreCase("true");
  }
  return result;
} // stringToBoolean()
//--------------------------------------------------------------------------------------------------
/**
* Converts a string value to its equivalent int value. If the string is not a valid int value then
* the default value is returned. If there are commas in the value then they will be removed before
* converting to an integer (this makes this non-portable to some locations).
* @param stringValue The string that will be converted.
* @param defaultValue The value that will be returned if the string is invalid.
* @return The int value (using Integer.parseInt or the defaultValue if the string is not an int
* value).
*/
public static int stringToInt(final String stringValue, final long defaultValue) {
  int result;
  try {
    result = Integer.parseInt(stringToNumericAdjust(stringValue, true));
  }
  catch (Exception e) {
    result = (int)defaultValue;
  }
  return result;
} // stringToInt()
//--------------------------------------------------------------------------------------------------
/**
* Converts a string value to its equivalent long value. If the string is not a valid long value
* then the default value is returned. If there are commas in the value then they will be removed
* before converting to a long (this makes this non-portable to some locations).
* @param stringValue The string that will be converted.
* @param defaultValue The value that will be returned if the string is invalid.
* @return The long value (using <code>Long.parseLong</code> or the defaultValue if the string is
* not a valid <code>long</code> value).
*/
public static long stringToLong(final String stringValue, final long defaultValue) {
  long result;
  try {
    result = Long.parseLong(stringToNumericAdjust(stringValue, true));
  }
  catch (Exception e) {
    result = defaultValue;
  }
  return result;
} // stringToLong()
//--------------------------------------------------------------------------------------------------
private static String stringToNumericAdjust(final String stringValue, final boolean removeDecimals) {
  String result = stringValue.toString();
  if (result.indexOf(',') > 0) {
    result = result.replace(",", "");
  }
  if (result.length() > 0 && (result.charAt(0) == ' ' || result.charAt(result.length() - 1) == ' ')) {
    result = result.trim();
  }
  if (result.endsWith("-") && result.length() > 1) {
    result = "-" + result.substring(0, result.length() - 1);
  }
  if (result.startsWith("+") && result.length() > 1) {
    result = result.substring(1);
  }
  if (result.length() > 0 && (result.charAt(0) == ' ' || result.charAt(result.length() - 1) == ' ')) {
    result = result.trim();
  }
  if (removeDecimals) {
    int dotIndex = result.indexOf('.');
    if (dotIndex == 0) {
      result = "0";
    }
    else if (dotIndex > 0) {
      result = result.substring(0, dotIndex);
    }
  }
  return result;
} // stringToNumericAdjust()
//--------------------------------------------------------------------------------------------------
/**
* Decodes a UTF8 string into plain ASCII.
* @param origUTF8 The UTF8 string that is to be converted.
* @param percentIsHex If <code>true</code> then a percent sign will be used as an indication that
* the next two characters represent two hexadecimal digits.
* @return The <code>StringBuilder</code> containing the resulting conversion.
*/
public static StringBuilder utf8Decode(final String origUTF8, final boolean percentIsHex) {
  return utf8Decode(origUTF8, percentIsHex, null);
} // utf8Decode()
//--------------------------------------------------------------------------------------------------
/**
* Decodes a UTF8 string into plain ASCII.
* @param origUTF8 The UTF8 string that is to be converted.
* @param percentOrAmpersandIsASCII If <code>true</code> then a percent sign will be used as an
* indication that the next two characters represent two hexadecimal digits or that "&#" and ";"
* surround a decimal ASCII code (e.g., "&#38;" represents an ampersand).
* @param sb The <code>StringBuilder</code> that will contain the resulting conversion. If this is
* <code>null</code> then a new <code>StringBuilder</code> will be created and returned.
* @return The <code>StringBuilder</code> containing the resulting conversion.
*/
public static StringBuilder utf8Decode(final String origUTF8,
                                       final boolean percentOrAmpersandIsASCII,
                                       final StringBuilder sb) {
  StringBuilder result;
  if (sb == null) {
    result = new StringBuilder();
  }
  else {
    result = sb;
    result.setLength(0);
  }
  if (!isBlank(origUTF8)) {
    String afterHexUTF8 = "";
    if (percentOrAmpersandIsASCII && (origUTF8.indexOf('%') >= 0 || origUTF8.contains("&#"))) {
      for (int i = 0; i < origUTF8.length();) {
        char nextChar = origUTF8.charAt(i);
        if (nextChar == '%' && origUTF8.length() > i + 2) { // "%xx" for a hex value
          long longValue = hexToLong(origUTF8.substring(i + 1, i + 3), 256);
          if (longValue == 256) {
            ++i;
          }
          else {
            nextChar = (char)longValue;
            i += 3;
          }
        }
        else if (nextChar == '&' && origUTF8.length() > i + 4) {
          if (origUTF8.charAt(i + 1) == '#') { // "&#99;" or "&#999;"
            int semicolonIndex = 0;
            if (origUTF8.charAt(i + 4) == ';') {
              semicolonIndex = i + 4;
            }
            else if (origUTF8.charAt(i + 5) == ';') {
              semicolonIndex = i + 5;
            }
            else {
              ++i;
            }
            if (semicolonIndex > 0) {
              int intValue = stringToInt(origUTF8.substring(i + 2, semicolonIndex), 1000);
              if (intValue == 1000) {
                ++i;
              }
              else {
                nextChar = (char)intValue;
                i = semicolonIndex + 1;
              }
            }
          }
          else {
            ++i;
          }
        }
        else {
          ++i;
        }
        afterHexUTF8 += nextChar;
      }
    }
    else {
      afterHexUTF8 = origUTF8;
    }
    for (int i = 0; i < afterHexUTF8.length(); ++i) {
      long byteValue = afterHexUTF8.charAt(i);
      if ((byteValue & 0x80) != 0) {
        if ((byteValue & 0xe0) == 0xc0) {
          byteValue = ((byteValue & 0x1f) << 6) + (afterHexUTF8.charAt(++i) & 0x3f);
        }
        else if ((byteValue & 0xf0) == 0xe0) {
          byteValue = ((byteValue & 0x0f) << 12) + ((afterHexUTF8.charAt(++i) & 0x3f) << 6) +
                      (afterHexUTF8.charAt(++i) & 0x3f);
        }
        else {
          byteValue = ((byteValue & 0x07) << 18) + ((afterHexUTF8.charAt(++i) & 0x3f) << 12) +
                      ((afterHexUTF8.charAt(++i) & 0x3f) << 6) + (afterHexUTF8.charAt(++i) & 0x3f);
        }
      }
      result.append((char)byteValue);
    }
  }
  return result;
} // utf8Decode()
//--------------------------------------------------------------------------------------------------
static void validateElementName(final Node elementNode, final String namevalue,
                                final String exceptionMessage) throws ITunesException {
  if (elementNode == null || !elementNode.getNodeName().equals(namevalue)) {
    throw new ITunesException(exceptionMessage);
  }
} // validateElementName()
//--------------------------------------------------------------------------------------------------
static void validateElementValue(final Element element, final String value,
                                 final String exceptionMessage) throws ITunesException {
  if (element == null) {
    throw new ITunesException(exceptionMessage);
  }
  if (!ITunesUtil.getElementValue(element).equals(value)) {
    throw new ITunesException(exceptionMessage);
  }
} // validateElementValue()
//--------------------------------------------------------------------------------------------------
}